use std::{collections::HashMap, time::Duration};

use ggez::{
    glam::Vec2,
    graphics::{self, DrawParam, InstanceArray},
    Context,
};

use itertools::Itertools;
use specs::{Join, Read, ReadStorage, System};

use crate::{components::RenderableKind, constants::TILE_WIDTH, resources::Time};
use crate::{
    components::{Position, Renderable},
    resources::Gameplay,
};

pub struct RenderingSystem<'a> {
    pub context: &'a mut Context,
}

impl<'a> System<'a> for RenderingSystem<'a> {
    type SystemData = (
        Read<'a, Time>,
        Read<'a, Gameplay>,
        ReadStorage<'a, Position>,
        ReadStorage<'a, Renderable>,
    );

    fn run(&mut self, data: Self::SystemData) {
        let (time, gameplay, positions, renderables) = data;
        // Clearing the screen (this gives us the background colour)
        // graphics::clear(self.context, graphics::Color::new(0.95, 0.95, 0.95, 1.0));
        let mut canvas: graphics::Canvas =
            graphics::Canvas::from_frame(self.context, graphics::Color::new(0.95, 0.95, 0.95, 1.0));
        // Get all the renderables with their positions and sort by the position z
        // This will allow us to have entities layered visually.
        let rendering_data = (&positions, &renderables).join().collect::<Vec<_>>();
        let mut rendering_batches: HashMap<u8, HashMap<String, Vec<DrawParam>>> = HashMap::new();
        // Iterate each of the renderables, determine which image path should be rendered
        // at which drawparams, and then add that to the rendering_batches.
        for (position, renderable) in rendering_data.iter() {
            // Load the image
            let image_path = self.get_image_path(renderable, time.delta);
            let x = position.x as f32 * TILE_WIDTH;
            let y = position.y as f32 * TILE_WIDTH;
            let z = position.z;

            rendering_batches
                .entry(z)
                .or_default()
                .entry(image_path)
                .or_default()
                .push(DrawParam::new().dest(Vec2::new(x, y)));
        }
        // Iterate spritebatches ordered by z and actually render each of them
        for (_z, group) in rendering_batches
            .iter()
            .sorted_by(|a, b| Ord::cmp(&a.0, &b.0))
        {
            for (path, params) in group {
                let image = graphics::Image::from_path(self.context, path).expect("expected image");
                let mut sprite_batch = InstanceArray::new(self.context, image);
                for param in params {
                    sprite_batch.push(*param);
                }
                canvas.draw(&sprite_batch, DrawParam::new());
            }
        }
        // Render any text
        draw_text(&mut canvas, &gameplay.state.to_string(), 525.0, 80.0);
        draw_text(&mut canvas, &gameplay.moves_count.to_string(), 525.0, 100.0);
        let fps = format!("FPS:{:.0}", self.context.time.fps());
        draw_text(&mut canvas, &fps, 525.0, 120.0);

        canvas.finish(self.context).expect("expected canvas");
    }
}

impl RenderingSystem<'_> {
    #[allow(unused)]
    pub fn get_image(&mut self, renderable: &Renderable, delta: Duration) -> graphics::Image {
        let path_index = match renderable.kind() {
            RenderableKind::Static => {
                // We only have one image, so we just return that
                0
            }
            RenderableKind::Animated => {
                // If we have multiple, we want to select the right one based on the delta time.
                // First we get the delta in milliseconds, we % by 1000 to get the milliseconds
                // only and finally we divide by 250 to get a number between 0 and 4. If it's 4
                // we technically are on the next iteration of the loop (or on 0), but we will let
                // the renderable handle this logic of wrapping frames.
                ((delta.as_millis() % 1000) / 250) as usize
            }
        };
        let image_path = renderable.path(path_index);
        graphics::Image::from_path(self.context, image_path).expect("expected image")
    }
    pub fn get_image_path(&mut self, renderable: &Renderable, delta: Duration) -> String {
        let path_index = match renderable.kind() {
            RenderableKind::Static => {
                // We only have one image, so we just return that
                0
            }
            RenderableKind::Animated => {
                // If we have multiple, we want to select the right one based on the delta time.
                // First we get the delta in milliseconds, we % by 1000 to get the milliseconds
                // only and finally we divide by 250 to get a number between 0 and 4. If it's 4
                // we technically are on the next iteration of the loop (or on 0), but we will let
                // the renderable handle this logic of wrapping frames.
                ((delta.as_millis() % 1000) / 250) as usize
            }
        };
        renderable.path(path_index)
    }
}

pub fn draw_text(canvas: &mut graphics::Canvas, text: &str, x: f32, y: f32) {
    let mut text = graphics::Text::new(text);
    text.set_scale(20.0);
    let draw_params = DrawParam::new()
        .color(graphics::Color::new(0.0, 0.0, 0.0, 1.0))
        .dest(Vec2::new(x, y));
    canvas.draw(&text, draw_params);
}
